
/*!
    \copyright  Copyright (c) 2024 Qualcomm Technologies International, Ltd.
                All Rights Reserved.
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \file
    @{
        \ingroup    adkfs
        \brief      AdkFs Read-Only Filesystem provider

*/
#include "adkfs_rofs_logging.h"
#include "adkfs_provider_if.h"

#include <panic.h>
#include <file.h>

#include <stdint.h>
#include <errno.h>
#include <string.h>

#define MAX_ADKFS_ROFS_PATHLEN     ((size_t)(64))

typedef struct {
    FILE_INDEX idx;
    const char* path;
    off_t offset;
    size_t size;
    unsigned char *ptr;
} rofs_file_t;

static const char *get_filename(const char *path)
{
    return path + ADKFS_PATH_SEPARATOR_LEN;
}

static int32_t AdkFsRofs_Fopen(rofs_file_t** handle, const char *path, adkfs_access_flags_t access)
{
    DEBUG_LOG("AdkFsRoFs_Fopen: path=%s access=enum:adkfs_access_flags_t:%u", path, access);
    PanicFalse(strlen(path) <= MAX_ADKFS_ROFS_PATHLEN);

    int32_t result = 0;
    const char* filename = get_filename(path);

    if (ADKFS_FOPEN_READ == access)
    {
        rofs_file_t *rofs_handle = PanicUnlessNew(rofs_file_t);
        memset(rofs_handle, 0, sizeof(*rofs_handle));
        DEBUG_LOG("AdkFsRoFs_Fopen: filename=%s", filename);
        rofs_handle->idx = FileFind(FILE_ROOT, filename, strlen(filename));
        if(FILE_NONE != rofs_handle->idx)
        {
            DEBUG_LOG("AdkFsRoFs_Fopen: file_index=%u", rofs_handle->idx);
            rofs_handle->path = filename;
            rofs_handle->size = FileSize(rofs_handle->idx);
            *handle = rofs_handle;
        }
        else
        {
            DEBUG_LOG_ERROR("AdkFsRoFs_Fopen: filename=%s not found", filename);
            result = ENOENT;
        }
    }
    else
    {
        DEBUG_LOG_PANIC("AdkFsRoFs_Fopen: invalid access=enum:adkfs_access_flags_t:%u. This filesystem is Read-Only", access);
    }

    return result;
}

static int32_t AdkFsRofs_Fsize(const char *path, size_t *fsize)
{
    int32_t result = 0;
    const char* filename = get_filename(path);

    FILE_INDEX idx = FileFind(FILE_ROOT, filename, strlen(filename));
    if (FILE_NONE != idx)
    {
        *fsize = FileSize(idx);
    }
    else
    {
        result = ENOENT;
    }

    return result;
}

static int32_t AdkFsRofs_Fmap(rofs_file_t *handle, const void **fbuffer)
{
    int32_t result = 0;

    DEBUG_LOG("AdkFsRofs_Fmap: index=%u, offset=%u, fsize=%u", handle->idx, handle->offset, handle->size);
    handle->ptr = FileMap(handle->idx, handle->offset, handle->size);

    if (!handle->ptr)
    {
        DEBUG_LOG_ERROR("AdkFsRofs_Fmap: Failed to map file index=%u, offset=%u, fsize=%u", handle->idx, handle->offset, handle->size);
        result = EIO;
    }

    *fbuffer = handle->ptr;
    return result;
}

int32_t AdkFsRofs_Funmap(rofs_file_t* handle)
{
    int32_t result = 0;
    if(FileUnmap(handle->ptr))
    {
        handle->ptr = NULL;
    }
    else
    {
        result = EIO;
    }
    return result;
}

int32_t AdkFsRofs_Fseek(rofs_file_t* handle, size_t offset)
{
    handle->offset = offset;
    return 0;
}

int32_t AdkFsRofs_Ftell(rofs_file_t* handle, size_t* offset)
{
    *offset = handle->offset;
    return 0;
}

static size_t truncate_read_to_fsize(rofs_file_t *handle, size_t read_size)
{
    size_t fsize = handle->size;

    if((handle->offset + read_size) > fsize)
    {
        return fsize - handle->offset;
    }

    return read_size;
}

int32_t AdkFsRofs_Fread(rofs_file_t *handle, void *buf_ptr, size_t read_size, size_t *bytes_read)
{
    PanicFalse(read_size > 0);

    DEBUG_LOG("AdkFsRofs_Fread: path=%s, handle->ptr=%p, buf_ptr=%p, read_size=%d", handle->path, handle->ptr, buf_ptr, read_size);

    if ((uintptr_t)handle->ptr <= (uintptr_t)buf_ptr && (uintptr_t)buf_ptr < (uintptr_t)(handle->ptr + handle->size))
    {
        /** If buf_ptr is in this range it means that the file contents have been mapped with Fmap
         * But update the value of bytes_read because the caller may be expecting that
         */
        *bytes_read = read_size;
        return 0;
    }

    *bytes_read = truncate_read_to_fsize(handle, read_size);

    memcpy(buf_ptr, (handle->ptr + handle->offset), *bytes_read);

    return 0;
}

int32_t AdkFsRofs_Fwrite(rofs_file_t* handle, const void *buf_ptr, size_t write_size, size_t *bytes_written)
{
    UNUSED(handle);
    UNUSED(buf_ptr);
    UNUSED(write_size);
    UNUSED(bytes_written);
    return EBADF;
}

int32_t AdkFsRofs_Fclose(rofs_file_t* handle)
{
    return AdkFsRofs_Funmap(handle);
}

int32_t AdkFsRofs_Fdelete(const char *path)
{
    UNUSED(path);
    return EPERM;
}

static const adkfs_provider_if_t adkfs_rofs_if = {
    .root_path = "/ro",
    .Fopen = AdkFsRofs_Fopen,
    .Fsize = AdkFsRofs_Fsize,
    .Fmap = AdkFsRofs_Fmap,
    .Funmap = AdkFsRofs_Funmap,
    .Fseek = AdkFsRofs_Fseek,
    .Ftell = AdkFsRofs_Ftell,
    .Fread = AdkFsRofs_Fread,
    .Fwrite = AdkFsRofs_Fwrite,
    .Fclose = AdkFsRofs_Fclose,
    .Fdelete = AdkFsRofs_Fdelete,
};

bool AdkFs_RofsInit(Task init_task)
{
    UNUSED(init_task);

    AdkFs_RegisterProvider(&adkfs_rofs_if);

    return TRUE;
}
